<?php

    /**
    * Mukei Development Framework
    *
    * Mukei is a development framework system created to provide developers
    * two main features: a plugin architecture which and expand the
    * functionality of the system and an event system which allows exansion of
    * existing plugins without modifing the original code.  The Mukei
    * development framework system can speed up development of new web
    * applications and reduce maintenance time of existing code.
    *
    * This file is part of Mukei.
    *
    * Mukei is free software; you can redistribute it and/or modify it under
    * the terms of the GNU General Public License as published by the Free
    * Software Foundation; either version 3 of the License, or (at your option)
    * any later version.
    *
    * Mukei is distributed in the hope that it will be useful, but WITHOUT ANY
    * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    * details.
    *
    * You should have received a copy of the GNU General Public License
    * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    *
    * @package   Mukei
    * @version   0.2
    * @author    Kevin Herrera
    * @license   http://www.gnu.org/licenses/gpl.txt GNU GPL v3
    * @copyright Copyright (c) 2007 Kevin Herrera.
    */

    /**
    * Client System Plugin
    *
    * This system plugin is the portion of the system that allows client
    * plugins to exist.  Without this system plugin, the system would not know
    * how to handle client plugins or even recognize that they exist.  To be
    * able to access a client plugin a browser, the field "act" must have the
    * file name of the plugin without the PHP extension as a value.  All other
    * arguments passed through the query string will be ignored.  The act field
    * will be checked for paths and will use the defualt plugin if a path is
    * detected.  This is to prevent a malicious user from attempting to load
    * a file from a different part of the server.
    *
    * In order for a client plugin to be executed upon request, it has be in
    * the _config['clients'] array.  If the plugin is not in the array, the
    * default plugin will be used.  If the default plugin is not in the array,
    * a fatal error will be triggered.  This method also attaches a listener
    * to the event "m_final".  This will allow all system plugins to perform
    * any processes necessary before the client plugin is loaded.
    *
    * @package Client
    * @version 0.2
    * @final
    */
    final class Client
    {
        /**#@+
        * @access public
        * @var    const
        */
        const ADMIN    = true;
        const AUTHOR   = "Kevin Herrera";
        const DESC     = "Client plugins sub-system for Mukei.";
        const HANDLE   = "CLI";
        const NAME     = "Mukei Client Processor";
        const REQUIRED = "0.2";
        const VERSION  = "0.2";
        const WEBSITE  = "http://www.mukei.org/";
        /**#@-*/

        /**
        * @access private
        * @var    array
        */
        private $_config = array( );

        /**
        * Class Constructor
        *
        * This system method loads the Client settings file, checks to see if
        * the required clients directory has errors, reports its dependencies
        * to the system, and attaches an event listener to the "m_final"
        * system event.  If the required settings file does not exist, a fatal
        * error will be triggered.  If the clients directory does not exist or
        * has permission issues, a fatal error will be triggered.  The only
        * dependency this system plugin has is to the "Browser Agent" system
        * plugin on the "BRW" handle.  After all is successful, the event
        * listener, listen(), is attached to the "m_final" event for the final
        * process.
        *
        * @access public
        * @see    Client::listen()
        * @uses   m_data()
        * @uses   m_path()
        * @uses   Mukei::attach()
        * @uses   Mukei::depend()
        * @return object
        */
        public function __construct ( )
        {
            // Get global.
            global $M;

            // Load settings.
            if ( ( $this->_config = m_data( "Client" ) ) === null )
            {
                // Report fatal error.
                trigger_error( "Unable to load settings.",
                               E_USER_ERROR );
            }

            // Missing clients directory?
            if ( !defined( 'M_CLIENTS_DIR' ) )
            {
                // Report fatal error.
                trigger_error( "Require clients directory missing.",
                               E_USER_ERROR );
            }

            // Clients directory errors?
            elseif ( !m_path( M_CLIENTS_DIR,
                              M_DIRECTORY,
                              true ) )
            {
                // Report fatal error.
                trigger_error( "Client directory has errors.",
                               E_USER_ERROR );
            }

            // Add dependencies.
            $M->depend( array(

                'BRW' => "Browser",

            ) );

            // Attach a listener.
            $M->attach( "m_final",
                        array( $this,
                               "listen" ) );
        }

        /**
        * Check Default Plugin
        *
        * This system method will check to see if a default client plugin has
        * been configured.  If a default client plugin is configured, it will
        * then check to see if it exists.  The specified default client plugin
        * may also be a "fake" client plugin (a system plugin acting as a
        * client).  If the default client plugin does not exist, a fatal error
        * will be triggered.  If no default client plugin was defined, a fatal
        * error will be triggered.
        *
        * @access private
        * @uses   m_path()
        * @uses   Client::_scan()
        * @return void
        */
        private function _check ( )
        {
            // Default defined?
            if ( !empty( $this->_config['default'] ) )
            {
                // Get actions.
                $actions = $this->_scan( );

                // Create path.
                $path = M_CLIENTS_DIR . $this->_config['default'] . M_EXT;

                // Invalid default?
                if ( !isset( $actions[ $this->_config['default'] ] ) &&
                     !m_path( $path, M_FILE, true ) )
                {
                    // Report fatal error.
                    trigger_error( "Default client plugin, " .
                                   $this->_config['default'] .
                                   ", does not exist.",
                                   E_USER_ERROR );
                }
            }

            // No default plugin configured?
            else
            {
                // Report fatal error.
                trigger_error( "No default client plugin has been configured.",
                               E_USER_ERROR );
            }
        }

        /**
        * Scan Class Actions
        *
        * This system method scans all user defined classes for system plugins.
        * Once the system plugin classes have been found, this method will
        * attempt to find all loaded system plugins with the ACTION class
        * constant declared.  This method will then check to see if the
        * system plugin class constant METHOD is declared and if the method
        * exists.  If all of the checks passed, it will then check to see if
        * another system plugin is attempting to use the same action name.
        * If there are no conflicts and all the checks are passed, the system
        * plugin will be added to an action list and returned.  A fatal error
        * is only triggered if the system plugin declares the ACTION class
        * constant and the METHOD class constant does not exist or the method
        * specified in the constant does not exist.
        *
        * @access private
        * @uses   Mukei::classes()
        * @uses   Mukei::system()
        * @return array
        */
        private function _scan ( )
        {
            // Get global.
            global $M;

            // Get user defined classes.
            $classes = $M->classes( );

            // Initialize array.
            $return = array( );

            // Walk through each class.
            foreach ( $classes as $class )
            {
                // Class has HANDLE constant?
                if ( $handle = @constant( "${class}::HANDLE" ) )
                {
                    // Class has ACTION constant?
                    if ( $action = @constant( "${class}::ACTION" ) )
                    {
                        // Class is a loaded system plugin?
                        if ( $M->system( $handle, $class ) )
                        {
                            // Class has METHOD constant?
                            if ( $method = @constant( "${class}::METHOD" ) )
                            {
                                // Method exists?
                                if ( method_exists( $M->$handle, $method ) )
                                {
                                    // Action not in use?
                                    if ( !isset( $return[ $action ] ) )
                                    {
                                        // Set in return.
                                        $return[ $action ] = array(

                                            $handle,
                                            $method,

                                        );
                                    }

                                    // Action in use?
                                    else
                                    {
                                        // Report fatal error.
                                        trigger_error( "Client action, "      .
                                                       "$action, is already " .
                                                       "in use by, "          .
                                                       $return[ $action ][1]  .
                                                       ".",
                                                       E_USER_ERROR );
                                    }
                                }

                                // Method does not exist?
                                else
                                {
                                    // Report fatal error.
                                    trigger_error( "Action method, $method, " .
                                                   "does not exist in the "   .
                                                   "system plugin, $class.",
                                                   E_USER_ERROR );
                                }
                            }

                            // Missing METHOD constant?
                            else
                            {
                                // Report fatal error.
                                trigger_error( "System plugin, $class, did " .
                                               "not declare the class "      .
                                               "constant, METHOD.",
                                               E_USER_ERROR );
                            }
                        }
                    }
                }
            }

            return( $return );
        }

        /**
        * System Event Listener
        *
        * This system method will check the requested action (from the "act"
        * field in the query string), check to see if the default should be
        * used, and load the requested (or default) plugin.  If the plugin
        * name contains any path information, the default plugin will be used.
        * If the requested plugin is not in the allowed list, the default
        * plugin will be used.  If the default plugin needs to be used but
        * none has been configured, a fatal error will be triggered.  System
        * plugins may also act as a client plugin by defining the ACTION and
        * METHOD class constants.  However, if both a real client plugin and
        * a "fake" client plugin (a system plugin acting as a client) are using
        * the same action name, a fatal error will be triggered.
        *
        * @access public
        * @uses   m_path()
        * @uses   Browser::update()
        * @uses   Client::_check()
        * @uses   Client::_scan()
        * @return void
        */
        public function listen ( )
        {
            // Get global.
            global $M;

            // Initialize path string.
            $path = "";

            // No action?
            if ( empty( $M->BRW->ALL['act'] ) )
            {
                // Check default.
                $this->_check( );

                // Set default.
                $M->BRW->update( "act", $this->_config['default'] );
            }

            // Get actions.
            $actions = $this->_scan( );

            // Not default?
            if ( $M->BRW->_ALL['act'] !== $this->_config['default'] )
            {
                // Create path.
                $path = M_CLIENTS_DIR . $M->BRW->_ALL['act'] . M_EXT;

                // Request malicious?
                if ( preg_match( "/[\\\\|\/]/", $M->BRW->_ALL['act'] ) )
                {
                    // Check default.
                    $this->_check( );

                    // Set default.
                    $M->BRW->update( "act", $this->_config['default'] );

                    // Update path.
                    $path = M_CLIENTS_DIR . $M->BRW->_ALL['act'] . M_EXT;
                }

                // Path does not exist?
                elseif ( ( !isset( $actions[ $M->BRW->_ALL['act'] ] ) &&
                           !@m_path( $path, M_FILE, true ) ) ||
                         !in_array( $M->BRW->_ALL['act'],
                                    $this->_config['clients'] ) )
                {
                    // Check default.
                    $this->_check( );

                    // Set default.
                    $M->BRW->update( "act", $this->_config['default'] );

                    // Update path.
                    $path = M_CLIENTS_DIR . $M->BRW->_ALL['act'] . M_EXT;
                }
            }

            // No path?
            if ( empty( $path ) )
            {
                // Create path.
                $path = M_CLIENTS_DIR . $M->BRW->_ALL['act'] . M_EXT;
            }

            // Name conflict detected?
            if ( !empty( $actions[ $M->BRW->_ALL['act'] ] ) &&
                 @m_path( $path, M_FILE, true ) )
            {
                // Report fatal error.
                trigger_error( "Client plugin naming conflict has been "     .
                               "detected.  Both a client plugin and a "      .
                               "system plugin mimicking as a client plugin " .
                               "are attempting to use the same name.",
                               E_USER_ERROR );
            }

            // Is action?
            if ( !empty( $actions[ $M->BRW->_ALL['act'] ] ) )
            {
                // Create shortcut.
                $shortcut = $actions[ $M->BRW->_ALL['act'] ];

                // Execute system client.
                $M->$shortcut[0]->$shortcut[1]( );
            }

            // Is plugin?
            else
            {
                // Load client plugin.
                require_once( $path );
            }
        }
    }

?>